package com.googlecode.geoteacher;

import java.io.InputStream;
import java.net.URL;
import java.util.Date;

import android.app.Dialog;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.OnMapClickListener;
import com.google.android.gms.maps.GoogleMap.OnMapLongClickListener;
import com.google.android.gms.maps.GoogleMap.OnMarkerClickListener;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.PolygonOptions;
import com.googlecode.geoteacher.db.DataSource;
import com.googlecode.geoteacher.db.model.Challenge;
import com.googlecode.geoteacher.db.model.ChallengeResult;
import com.googlecode.geoteacher.logic.ChallengeFile;
import com.googlecode.geoteacher.logic.ChallengeFileReader;
import com.googlecode.geoteacher.logic.Match;
import com.googlecode.geoteacher.util.AssetsUtils;

public class MapActivity extends FragmentActivity implements
		OnMapClickListener, OnMarkerClickListener, OnMapLongClickListener {

	private static final String INTENT_PREFIX = "com.googlecode.geoteacher.SinglePlayer.";

	public static final String INTENT_CHALLENGE_ID = INTENT_PREFIX
			+ "challenge_id";

	private static final int PLAY_SERVICES_REQUEST = 1;

	private static final int MAX_TIME_SECONDS = 10;

	private static final int MAX_NUM_CHALLENGES = 10;

	private DataSource datasource;

	private static enum GameStatus {
		USER_PLAYS, DISPLAY_SOLUTION;
	}

	private GoogleMap mMap;
	private ChallengeFile challengeFile;
	private long challengeId;

	private Match match;
	private GameStatus status = null;
	private long startTime = 0;
	private AsyncTask<Void, Integer, Void> elapsedTimeTask;
	private int numOfChallenge = 0;

	private TextView whereIsDisplay;
	private Button nextChallengeButton;
	private ProgressBar progressBar;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_map);

		datasource = new DataSource(this);
		datasource.open();

		whereIsDisplay = (TextView) findViewById(R.id.where_is_display);
		nextChallengeButton = (Button) findViewById(R.id.next_challenge);
		progressBar = (ProgressBar) findViewById(R.id.progressBar);

		// prevent device orientation change
		setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);

		setUpMapIfNeeded();
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.map, menu);
		return true;
	}

	@Override
	protected void onPause() {
		datasource.close();
		super.onPause();
	}

	protected void onResume() {
		datasource.open();
		super.onResume();

		checkPlayServices();
		setUpMapIfNeeded();
	}
	
	@Override
	public void onBackPressed() {
	}

	private boolean checkPlayServices() {
		int isAvailable = GooglePlayServicesUtil
				.isGooglePlayServicesAvailable(this);

		if (isAvailable == ConnectionResult.SUCCESS) {
			return true;
		} else if (GooglePlayServicesUtil.isUserRecoverableError(isAvailable)) {
			Dialog dialog = GooglePlayServicesUtil.getErrorDialog(isAvailable,
					this, PLAY_SERVICES_REQUEST);
			dialog.show();
		} else {
			Toast.makeText(this, "Connect to Maps", Toast.LENGTH_SHORT).show();

		}
		return false;
	}

	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		if (requestCode == PLAY_SERVICES_REQUEST) {
			Log.w(MapActivity.class.getName(), "onActivityResult: "
					+ resultCode);
		}
	}

	public boolean onOptionsItemSelected(MenuItem item) {
		switch (item.getItemId()) {
		case R.id.action_about:
			startActivity(new Intent(this, AboutActivity.class));
			return true;
		}
		return super.onOptionsItemSelected(item);
	}

	private void setUpMapIfNeeded() {
		// Do a null check to confirm that we have not already instantiated the
		// map.
		if (mMap == null) {
			// Try to obtain the map from the SupportMapFragment.
			FragmentManager fragmentManager = getSupportFragmentManager();

			if (fragmentManager == null)
				throw new NullPointerException("FragmentManager is null");

			SupportMapFragment mapFragment = (SupportMapFragment) fragmentManager
					.findFragmentById(R.id.map);

			if (mapFragment == null)
				throw new NullPointerException("MapFragment is null");

			mMap = mapFragment.getMap();

			// Check if we were successful in obtaining the map.
			if (mMap != null) {
				setUpMap();
			}
		}

		if (challengeFile == null) {
			try {
				challengeId = getIntent().getExtras().getLong(
						INTENT_CHALLENGE_ID);

				Challenge ch = datasource.getChallenge(challengeId);
				URL url = new URL(ch.getPath());

				InputStream in = null;

				if (AssetsUtils.isAssetURL(url)) {
					in = getAssets().open(AssetsUtils.getRelativePath(url));
				} else {
					in = url.openStream();
				}

				challengeFile = ChallengeFileReader.read(in);
				in.close();
			} catch (Exception e) {
				e.printStackTrace();
			}

			if (challengeFile != null) {
				LatLngBounds bounds = challengeFile.getBounds();
				mMap.addPolygon(new PolygonOptions()
						.add(bounds.southwest)
						.add(new LatLng(bounds.southwest.latitude,
								bounds.northeast.longitude))
						.add(bounds.northeast)
						.add(new LatLng(bounds.northeast.latitude,
								bounds.southwest.longitude)).geodesic(true));

				match = new Match(challengeFile, MAX_NUM_CHALLENGES);
				match.displayPermanent(mMap);

				setUpChallenge();
			} else {
				Log.w(MapActivity.class.getName(), "placeList is null");
			}
		}
	}

	private void setUpMap() {
		mMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE);
		mMap.getUiSettings().setCompassEnabled(false);
		mMap.getUiSettings().setTiltGesturesEnabled(false);
		mMap.getUiSettings().setRotateGesturesEnabled(false);
		mMap.getUiSettings().setMyLocationButtonEnabled(false);
		mMap.getUiSettings().setZoomControlsEnabled(false);
		mMap.getUiSettings().setZoomGesturesEnabled(false);
		mMap.getUiSettings().setScrollGesturesEnabled(false);
		mMap.setBuildingsEnabled(false);
		mMap.setIndoorEnabled(false);
		mMap.setPadding(0, 100, 0, 0);
		Log.w(MapActivity.class.getName(),
				"height of info: " + String.valueOf(whereIsDisplay.getHeight()));

		mMap.setOnMapClickListener(this);
		mMap.setOnMapLongClickListener(this);
		mMap.setOnMarkerClickListener(this);
	}

	private boolean firstChallenge = true;

	private void setUpChallenge() {
		nextChallengeButton.setEnabled(false);
		nextChallengeButton.setVisibility(View.INVISIBLE);
		progressBar.setVisibility(View.VISIBLE);

		match.nextPlaceAndHideLastSolution(mMap);

		if (firstChallenge) {
			if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
				mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(
						challengeFile.getBounds(), this.getResources()
								.getDisplayMetrics().widthPixels,
						this.getResources().getDisplayMetrics().heightPixels, 0));
			} else {
				mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(
						challengeFile.getBounds(), this.getResources()
								.getDisplayMetrics().heightPixels,
						this.getResources().getDisplayMetrics().widthPixels, 0));
			}
			firstChallenge = false;
		} else {
			mMap.setPadding(0, whereIsDisplay.getHeight(), 0, 0);
			Log.w(MapActivity.class.getName(),
					"height of info: "
							+ String.valueOf(whereIsDisplay.getHeight()));

			mMap.animateCamera(CameraUpdateFactory.newLatLngBounds(
					challengeFile.getBounds(), 0));
		}

		whereIsDisplay.setText(String.format(
				getString(R.string.where_is_place), match.getCurrentPlace()
						.getName()));

		progressBar.setMax(MAX_TIME_SECONDS * 1000);

		elapsedTimeTask = new AsyncTask<Void, Integer, Void>() {
			protected void onPreExecute() {
				startTime = System.currentTimeMillis();
			}

			protected Void doInBackground(Void... params) {
				publishProgress(0);

				int diffMillis = 0;

				while (!isCancelled() && diffMillis < MAX_TIME_SECONDS * 1000) {
					try {
						Thread.sleep(40);
					} catch (InterruptedException e) {
					}

					long currentTime = System.currentTimeMillis();
					diffMillis = (int) (currentTime - startTime);

					publishProgress(diffMillis);
				}

				return null;
			}

			protected void onPostExecute(Void result) {
				selectedSolution(null); // time elapsed
			}

			protected void onProgressUpdate(Integer... progress) {
				progressBar.setProgress(progress[0]);
			}
		}.execute();

		numOfChallenge++;
		setTitle(numOfChallenge + " / " + MAX_NUM_CHALLENGES);
		if (numOfChallenge == MAX_NUM_CHALLENGES) {
			nextChallengeButton.setText(R.string.finish_match);
		}

		status = GameStatus.USER_PLAYS;
	}

	/**
	 * If latLng is null then the time elapsed before the user selected an
	 * answer.
	 * 
	 * @param latLng
	 */
	private void selectedSolution(LatLng latLng) {

		if (elapsedTimeTask != null && latLng != null) {
			elapsedTimeTask.cancel(false);
		}

		double distance = match.saveResultAndShowSolution(latLng, mMap);

		if (latLng != null) {

			whereIsDisplay.setText(String.format(
					getString(R.string.solution_message),
					Math.round(distance / 1000)));
		} else {

			whereIsDisplay.setText(R.string.time_elapsed);

		}

		status = GameStatus.DISPLAY_SOLUTION;
		nextChallengeButton.setEnabled(true);
		progressBar.setVisibility(View.INVISIBLE);
		nextChallengeButton.setVisibility(View.VISIBLE);

		// mMap.animateCamera(CameraUpdateFactory.newLatLngBounds(
		// challenge.createBoundsWith(latLng), 100));

		// setUpChallenge();
	}

	@Override
	public void onMapClick(LatLng latLng) {
		if (status == GameStatus.USER_PLAYS)
			selectedSolution(latLng);
	}

	@Override
	public void onMapLongClick(LatLng latLng) {
		this.onMapClick(latLng);
	}

	public void onNextChallenge(View view) {
		if (status == GameStatus.DISPLAY_SOLUTION) {

			if (numOfChallenge == MAX_NUM_CHALLENGES) {
				saveResult();

				Intent intent = new Intent(this, EvaluationActivity.class);
				intent.putExtra(EvaluationActivity.INTENT_AVERAGE_DISTANCE,
						match.getAvgDist());
				intent.putExtra(EvaluationActivity.INTENT_LIST_PLACES,
						match.getPlacesArray());
				intent.putExtra(EvaluationActivity.INTENT_LIST_DISTANCES,
						match.getDistancesArray());
				intent.putExtra(EvaluationActivity.INTENT_CHALLENGE_ID,
						challengeId);
				intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
				startActivity(intent);
			} else {
				setUpChallenge();
			}
		}
	}

	@Override
	public boolean onMarkerClick(Marker m) {
		m.showInfoWindow();

		return true; // prevent moving camera to marker
	}

	private void saveResult() {
		int avgDistance = match.getAvgDist();
		String actualHash = challengeFile.getHash();

		// updates hash if needed and returns true if hash was changed
		boolean hashChanged = datasource.updateChallengeHashIfNeeded(
				challengeId, actualHash);

		if (hashChanged) {

			Log.w(EvaluationActivity.class.getName(),
					"Hashes were not equal. Delete all ChallengeResults with challengeId: "
							+ challengeId);

			// delete all current results of that challenge
			datasource.deleteAllChallengeResultsOfChallenge(challengeId);
		}

		ChallengeResult result = new ChallengeResult(challengeId, avgDistance,
				new Date());

		datasource.createChallengeResult(result);
	}
}